/* Copyright 2009 Sony Mathew, Minneapolis MN. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
package com.xsm.gwt.remoteevent.server;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.xsm.gwt.remoteevent.client.RemoteEventProxy;
import com.xsm.lite.event.EventManager;
import com.xsm.lite.event.RemoteRequestEvent;
import com.xsm.lite.event.RemoteResponseEvent;
import com.xsm.servlet.ServletBindingProvider;

/**
 * Expects to have the following init parameter configured.
 *      ServletBindingProvider.PARAM_KEY = fully.qualified.class.name.of.binding.provider.that.provides.my.ioc.context.
 *      
 * @author Sony Mathew
 */
public class RemoteEventProxyServlet extends RemoteServiceServlet implements RemoteEventProxy {	
    private static final long serialVersionUID = 1L;

    private ServletBindingProvider bindingProvider = null;
    private boolean invalidateSessionOnUnexpectedFailure;
    
    /**
     * My IoC Context.
     */
    public interface Context {
        public EventManager getEventManager();
    }
    
    /**
     * Checks to see if a session is required for the specified event, and if
     * so throws an exception if there isn't one.
     * @param e
     */
    private void checkSession(RemoteRequestEvent e) {
    	//	if the event is one that doesn't require a session, we can
    	//	just return without checking if the session is valid
    	if ( ! e.isRequiresSession()) {
			return;
		}
    	
    	//	if we got here, a session is required, so throw an exception if
    	//	we don't have one.
    	HttpSession session = getThreadLocalRequest().getSession(false);
    	if (session == null) {
    		throw new RemoteEventInvalidSessionException("Invalid session processing remote request");
    	}
    }
    
    /* (non-Javadoc)
	 * @see com.google.gwt.user.server.rpc.RemoteServiceServlet#doUnexpectedFailure(java.lang.Throwable)
	 */
	@Override
	protected void doUnexpectedFailure(Throwable e) {
		boolean isSessionTimeout = false;
	    if (e instanceof RemoteEventInvalidSessionException
	    	|| ( e.getCause() != null
	    		&& e.getCause() instanceof RemoteEventInvalidSessionException)) {
	    	isSessionTimeout = true;
    		debug("Session timeout dispatching incoming RPC call");
	    } else {
		    error("Exception while dispatching incoming RPC call", e);	
		    if (this.invalidateSessionOnUnexpectedFailure) {
		    	HttpSession session = getThreadLocalRequest().getSession(false);
		    	if (session != null) {
		    		try {
						session.invalidate();
						info("Invalidated the session");
					} catch (Exception ex) {
						warn("Error invalidating session", ex);
					}
		    	}
		    }
	    }
	    	    
	    // Send logging id with 500 status.
		HttpServletResponse response = getThreadLocalResponse();
	    try {
	      response.setContentType("text/plain");
	      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	      response.getWriter().write(isSessionTimeout ? "Session Timeout" : getProblemId());
	    } catch (IOException ex) {
	    	warn("Error sending unexpected failure message back to client", ex );
	    }
	}

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        String bindingProviderClassName = config.getInitParameter(ServletBindingProvider.PARAM_KEY);
        
        try {
            Class bindingProviderClass = Class.forName(bindingProviderClassName);
            bindingProvider = (ServletBindingProvider)bindingProviderClass.newInstance();
        } catch(Exception x) {
            throw new ServletException("Could not instantiate ServletBindingProvider [" + bindingProviderClassName + "]", x);
        }
        
        String invalidateSessionParm = config.getInitParameter("invalidateSessionOnUnexpectedFailure");
        this.invalidateSessionOnUnexpectedFailure = "true".equalsIgnoreCase(invalidateSessionParm)
        	|| "yes".equalsIgnoreCase(invalidateSessionParm) || "1".equals(invalidateSessionParm);
    }

    public Context getContext() {
        if (bindingProvider == null) {
            throw new IllegalStateException("No ServletBindingProvider configured");
        }
        
        return (Context)bindingProvider.getServletBinding(this, getThreadLocalRequest());
    }
    
    /**
     * Process the event by using the EventManager as the mediator.
     * 
     * @see com.xsm.gwt.remoteevent.client.RemoteEventProxy#process(com.xsm.lite.event.RemoteRequestEvent)
     */
    public RemoteResponseEvent process(RemoteRequestEvent reqe) {
    	
		debug("Process remote request: " + reqe.toString());
    	
    	//	check if a session is required for this event. If so, throws an exception
    	//	if the session does not exist.
    	checkSession(reqe);
	
        EventManager em = getContext().getEventManager();
        em.fire(reqe);
        
        RemoteResponseEvent rese = reqe.process(em);   	
        
		debug("Remote response: " + rese.toString());
       
        em.fire(rese);
        return rese;
    }

    /**
     * Override to return a unique id for unexpected problems (e.g. if users want to identify the problem). 
     */
    protected String getProblemId() {
        return "";
    }

    /**
     * Override for your needs. 
     */    
    protected void debug(String msg) {
        System.out.println("RemoteEventProxyServlet::DEBUG::" + msg); 
    }

    /**
     * Override for your needs. 
     */        
    protected void info(String msg) {
        System.out.println("RemoteEventProxyServlet::INFO::" + msg);
    }
    
    /**
     * Override for your needs. 
     */        
    protected void warn(String msg, Throwable x) {
        System.out.println("RemoteEventProxyServlet::WARN::" + msg);
        x.printStackTrace();
    }
    
    /**
     * Override for your needs. 
     */        
    protected void error(String msg, Throwable x) {
        System.out.println("RemoteEventProxyServlet::ERROR::" + msg);
        x.printStackTrace();
    }
    
}
